package com.google.code.stalkernet.example.ui;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;

import com.google.code.stalkernet.database.Database;
import com.google.code.stalkernet.database.event.DatabaseEvent;
import com.google.code.stalkernet.database.event.DatabaseListener;
import com.google.code.stalkernet.entry.Entry;
import com.google.code.stalkernet.entry.EntryID;
import com.google.code.stalkernet.reactor.Reactor;

public class DatabaseTableModel
    extends AbstractTableModel implements DatabaseListener
{
    private final static Logger logger =
        Logger.getLogger (DatabaseTableModel.class.getName ());

    private final static Charset UTF8 = Charset.forName ("UTF-8");

    private final Reactor reactor;
    private final Database database;

    private final List <Entry> entries = new ArrayList <> ();

    public DatabaseTableModel (Reactor reactor, Database database)
    {
        if (reactor == null)
            throw new IllegalArgumentException ("Reactor is null");

        if (database == null)
            throw new IllegalArgumentException ("Database is null");

        this.reactor = reactor;
        this.database = database;

        reactor.invoke (new DumpEntriesRunnable ());
    }

    @Override
    public int getRowCount ()
    {
        return entries.size ();
    }

    @Override
    public int getColumnCount ()
    {
        return 3;
    }

    @Override
    public Object getValueAt (int rowIndex, int columnIndex)
    {
        Entry entry = entries.get (rowIndex);

        switch (columnIndex)
        {
        case 0:
            return entry.getID ();
        case 1:
            return new String (entry.getBody ().getBytes (), UTF8);
        case 2:
            return toString (entry.getParentIDs ());
        default:
            throw new IllegalArgumentException (
                "Invalid column index: " + columnIndex);
        }
    }

    @Override
    public String getColumnName (int column)
    {
        switch (column)
        {
        case 0:
            return "ID";
        case 1:
            return "Body";
        case 2:
            return "Parents";
        default:
            throw new IllegalArgumentException (
                "Invalid column: " + column);
        }
    }

    @Override
    public void entryAdded (DatabaseEvent databaseEvent)
    {
        final Entry entry = databaseEvent.getEntry ();

        SwingUtilities.invokeLater (
            new Runnable()
            {
                @Override
                public void run ()
                {
                    int index = entries.size ();
                    entries.add (index, entry);
                    fireTableRowsInserted (index, index);
                }
            });
    }

    private static String toString (EntryID [] entryIDs)
    {
        StringBuffer result = new StringBuffer ();

        for (EntryID entryID: entryIDs)
        {
            if (result.length () > 0) result.append (", ");
            result.append (entryID);
        }
        return result.toString ();
    }

    private class DumpEntriesRunnable implements Runnable
    {
        @Override
        public void run ()
        {
            final Set <EntryID> entryIDs = new HashSet <> ();
            for (EntryID leafID: database.getLeafIDs ())
                addEntriesRecursively (leafID, entryIDs);

            final List <Entry> entries =
                new ArrayList<> (entryIDs.size ());
            for (EntryID entryID: entryIDs)
                entries.add (database.getEntry (entryID));

            SwingUtilities.invokeLater (
                new AddEntriesRunnable (
                    entries.toArray (new Entry [entries.size ()])));

            database.addDatabaseListener (DatabaseTableModel.this);
        }

        private void addEntriesRecursively (
            EntryID entryID, Collection <EntryID> entryIDs)
        {
            if (entryID == null)
                throw new IllegalArgumentException ("EntryID is null");

            if (entryIDs == null)
                throw new IllegalArgumentException ("Entry IDs is null");

            if (entryIDs.add (entryID))
            {
                for (EntryID parentID:
                    database.getEntry (entryID).getParentIDs ())
                    addEntriesRecursively (parentID, entryIDs);
            }
        }
    }

    private class AddEntriesRunnable implements Runnable
    {
        private final Entry [] entries;

        public AddEntriesRunnable (Entry [] entries)
        {
            if (entries == null)
                throw new IllegalArgumentException ("Entries is null");

            this.entries = entries.clone ();
        }

        @Override
        public void run ()
        {
            int entriesLength = entries.length;
            if (entriesLength > 0)
            {
                int index = DatabaseTableModel.this.entries.size ();

                DatabaseTableModel.this.entries.addAll (
                    Arrays.asList (entries));

                fireTableRowsInserted (
                    index, index + entriesLength - 1);
            }
        }
    }
}
